package org.dh2580.netty.demo.netty.protocol.http;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.CharsetUtil;

/**
 * HttpRequestDecoder
 * <p>
 * 将TCP报文转换为Http消息对象
 * </p>
 * <p>
 * HttpObjectAggregator
 * <p>
 * 将多个Http消息转换为单一的FullHttpRequest，或FullHttpResponse。
 * 因为HttpRequestDecoder/HttpResponseDecoder解码TCP报文会产生多个Http消息对象：HttpRequest/HttpResponse、HttpContent、LastHttpContent
 * </p>
 * <p>
 * ChunkedWriteHandler
 * <p>
 * 支持异步发送大的码流，常用于大文件传输场景，且不占用过多内存，防止发生Java内存溢出
 * </p>
 */
public class HttpEchoServer {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast("http-decoder", new HttpRequestDecoder());
                            socketChannel.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                            socketChannel.pipeline().addLast("http-encoder", new HttpResponseEncoder());
                            socketChannel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
                            socketChannel.pipeline().addLast("http-echo-server-handler", new HttpEchoServerHandler());
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(10026).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static class HttpEchoServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        protected void messageReceived(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
            if (!fullHttpRequest.getDecoderResult().isSuccess()) {
                sendError(channelHandlerContext, HttpResponseStatus.BAD_REQUEST);
                return;
            }

            if (fullHttpRequest.getMethod() != HttpMethod.GET) {
                sendError(channelHandlerContext, HttpResponseStatus.METHOD_NOT_ALLOWED);
                return;
            }

            String uri = fullHttpRequest.getUri();
            System.out.println("uri -> " + uri);

            String msg = "hello,word";

            HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

            HttpHeaders.setContentLength(response, msg.getBytes(CharsetUtil.UTF_8).length);
            HttpHeaders.setHeader(response, HttpHeaders.Names.CONTENT_TYPE, "application/json;charset=utf8");

            if (HttpHeaders.isKeepAlive(fullHttpRequest)) {
                fullHttpRequest.headers().set(HttpHeaders.Names.CONNECTION, true);
            }

            ByteBuf buf = Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8);
            ((DefaultFullHttpResponse) response).content().writeBytes(buf);
            buf.release();

            channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();

            if (ctx.channel().isActive()) {
                sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
        }

        private void sendError(ChannelHandlerContext context, HttpResponseStatus status) {
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8");

            ByteBuf failBuf = Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8);

            response.content().writeBytes(failBuf);

            context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }

}